
// ToolsLib Project

/* ToolsLib library for RusRoute firewall and other projects of
 * Andrey A. Moiseenko / IE Moiseenko A.A. (Russia)
 * e-mail: support@maasoftware.ru, maa2002@mail.ru
 * web: http://maasoftware.ru, http://maasoftware.com, http://maasoft.ru, http://maasoft.org
 * Author's full name: Andrey Alekseevitch Moiseenko
 * (russian name: Моисеенко Андрей Алексеевич)
 */

// ToolsLib/SysInfo.cpp

/* Copyright (C) 2002-2024 Andrey A. Moiseenko (support@maasoftware.ru)
 * All rights reserved.
 *
 * This library contains cross-platform class for simple reference count
 * implementation.
 * The library implementation written
 * by Andrey A. Moiseenko (support@maasoftware.ru).
 * This library and applications are
 * FREE FOR COMMERCIAL AND NON-COMMERCIAL USE
 * as long as the following conditions are aheared to.
 *
 * Copyright remains Andrey A. Moiseenko, and as such any Copyright notices in
 * the code are not to be removed.  If this code is used in a product,
 * Andrey A. Moiseenko should be given attribution as the author of the parts used.
 * This can be in the form of a textual message at program startup or
 * in documentation (online or textual) provided with the package.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *    This product includes software developed by Andrey A. Moiseenko (support@maasoftware.ru)
 *
 * THIS SOFTWARE IS PROVIDED BY ANDREY A. MOISEENKO ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * The licence and distribution terms for any publically available version or
 * derivative of this code cannot be changed.  i.e. this code cannot simply be
 * copied and put under another distribution licence
 * [including the GNU Public Licence.]
 */

#include "perm.h"
#include "temp.h"

//------------------------------------------------------------------------------
// CPU
//------------------------------------------------------------------------------

#ifdef _WIN32
typedef BOOL (WINAPI *LPFN_GLPI)(PSYSTEM_LOGICAL_PROCESSOR_INFORMATION, PDWORD);

int MaaGetActiveProcessorCoresCount()
{
    int procCoreCount = 0;
    char * buffer = nullptr;

    try
    {
        static CMaaDllProc dp;
        LPFN_GLPI Glpi = (LPFN_GLPI)dp.Get(L"Kernel32.dll", "GetLogicalProcessorInformation");
        if  (!Glpi)
        {
            //_tprintf(TEXT("GetLogicalProcessorInformation is not supported.\n"));
            throw 1;
        }

        DWORD returnLength = 0;

        while(1)
        {
            if  (Glpi((PSYSTEM_LOGICAL_PROCESSOR_INFORMATION)buffer, &returnLength))
            {
                break;
            }
            if  (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
            {
                delete [] buffer;
                buffer = nullptr;

                buffer = TL_NEW char[returnLength];

                if  (!buffer)
                {
                    throw 2;
                    //_tprintf(TEXT("Allocation failure\n"));
                    //return (2);
                }
            }
            else
            {
                //_tprintf(TEXT("Error %d\n"), GetLastError());
                throw 3;
            }
        }

        DWORD byteOffset = 0;
        PSYSTEM_LOGICAL_PROCESSOR_INFORMATION ptr = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION)buffer;

        while((byteOffset += sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION)) <= returnLength)
        {
            switch(ptr->Relationship)
            {
            case RelationProcessorCore:
                procCoreCount++;
                break;
            default:
                break;
            }
            //byteOffset += sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION);
            ptr++;
        }
    }
    catch(...)
    {
    }
    //_tprintf(TEXT("Number of active processor cores: %d\n"), procCoreCount);
    delete [] buffer;

    return procCoreCount > 0 ? procCoreCount : 0;
}
#endif

int CMaaGetCpuCount() noexcept
{
    static int numProcessors{0};
    if  (!numProcessors)
    {
#ifdef _WIN32
        try
        {
            numProcessors = MaaGetActiveProcessorCoresCount();
        }
        catch(...)
        {
        }
#endif
#ifdef __linux__
        FILE * file = fopen("/proc/cpuinfo", "r");
        if  (file)
        {
            int n = 0;
            char line[128];
            while(fgets(line, 128, file))
            {
                if  (CMaaIsMemEq(line, "processor", 9))
                {
                    n++;
                }
            }
            fclose(file);
            numProcessors = n ? n : 1;
        }
        else
        {
            numProcessors = 1;
        }
#endif
        if  (!numProcessors)
        {
            numProcessors = 1;
        }
    }
    return numProcessors;
}

#ifdef _WIN32
CMaaCpuUsage::CMaaCpuUsage()
{
    SYSTEM_INFO sysInfo;
    FILETIME ftime, fsys, fuser;

    GetSystemInfo(&sysInfo);
    //numProcessors = sysInfo.dwNumberOfProcessors;

    GetSystemTimeAsFileTime(&ftime);
    memcpy(&lastCPU, &ftime, sizeof(FILETIME));

    self = GetCurrentProcess();
    GetProcessTimes(self, &ftime, &ftime, &fsys, &fuser);
    memcpy(&lastSysCPU, &fsys, sizeof(FILETIME));
    memcpy(&lastUserCPU, &fuser, sizeof(FILETIME));
}

double CMaaCpuUsage::Get(double *pSys, double *pUsr, int *pCores)
{
    double All;

    FILETIME ftime, fsys, fuser;
    ULARGE_INTEGER now, sys, user;

    GetSystemTimeAsFileTime(&ftime);
    memcpy(&now, &ftime, sizeof(FILETIME));

    GetProcessTimes(self, &ftime, &ftime, &fsys, &fuser);
    memcpy(&sys, &fsys, sizeof(FILETIME));
    memcpy(&user, &fuser, sizeof(FILETIME));

    if  (now.QuadPart <= lastCPU.QuadPart)
    {
        //Overflow detection. Just skip this value.
        All = -1.0;
    }
    else
    {
        int c = CMaaGetCpuCount();
        double k = 100.0 / (now.QuadPart - lastCPU.QuadPart) / c;
        All = k * ((sys.QuadPart - lastSysCPU.QuadPart) + (user.QuadPart - lastUserCPU.QuadPart));
        if  (pSys)
        {
            *pSys = k * (sys.QuadPart - lastSysCPU.QuadPart);
        }
        if  (pUsr)
        {
            *pUsr = k * (user.QuadPart - lastUserCPU.QuadPart);
        }
        if  (pCores)
        {
            *pCores = c;
        }
    }
    lastCPU = now;
    lastUserCPU = user;
    lastSysCPU = sys;

    return All;
}
#endif

#ifdef __linux__
CMaaCpuUsage::CMaaCpuUsage()
{
    tms timeSample;
    lastCPU = times(&timeSample);
    lastSysCPU = timeSample.tms_stime;
    lastUserCPU = timeSample.tms_utime;
}

double CMaaCpuUsage::Get(double *pSys, double *pUsr, int *pCores)
{
    double All;

    tms timeSample;
    clock_t now;

    now = times(&timeSample);
    if  (now <= lastCPU || timeSample.tms_stime < lastSysCPU || timeSample.tms_utime < lastUserCPU)
    {
        //Overflow detection. Just skip this value.
        All = -1.0;
    }
    else
    {
        int c = CMaaGetCpuCount();
        double k = 100.0 / (now - lastCPU) / c;
        All = k * ((timeSample.tms_stime - lastSysCPU) + (timeSample.tms_utime - lastUserCPU));
        if  (pSys)
        {
            *pSys = k * (timeSample.tms_stime - lastSysCPU);
        }
        if  (pUsr)
        {
            *pUsr = k * (timeSample.tms_utime - lastUserCPU);
        }
        if  (pCores)
        {
            *pCores = c;
        }
    }
    lastCPU = now;
    lastSysCPU = timeSample.tms_stime;
    lastUserCPU = timeSample.tms_utime;

    return All;
}
#endif

int CMaaCpuUsage::Print()
{
    double All, Sys, Usr;
    int c;
    All = Get(&Sys, &Usr, &c);
    if  (All >= 0)
    {
        return __utf8_printf("cpu usage: sys=%.1lf, usr=%.1lf, sum=%.1lf, %d cores\n", Sys, Usr, All, c);
    }
    return 0;
}

//------------------------------------------------------------------------------
// Memory
//------------------------------------------------------------------------------

#ifdef __unix__
#include <malloc.h>
static _qword CTOOGetHeapAllocated()
{
    struct mallinfo2 m;
    m = mallinfo2();
    return m.arena;
}
static _qword CTOOGetMaxRss()
{
    rusage ru;
    if  (!getrusage(RUSAGE_SELF, &ru))
    {
        return ru.ru_maxrss * 1024;
    }
    return 0;
}
#else
#if 0
#include <malloc.h>
static _qword CTOOGetHeapAllocated()
{
    // slow, is not thread safe
    _HEAPINFO hinfo;
    int heapstatus;
    int numLoops;
    hinfo._pentry = nullptr;
    numLoops = 0;
    _qword s = 0;
    while((heapstatus = _heapwalk(&hinfo)) == _HEAPOK && numLoops < 0x7ffffff)
    {
        if  (hinfo._useflag == _USEDENTRY)
        {
            s += hinfo._size;
        }
        //printf("%6s block at %Fp of size %4.4X\n", (hinfo._useflag == _USEDENTRY ? "USED" : "FREE"), hinfo._pentry, hinfo._size);
        numLoops++;
    }

    /*
    switch(heapstatus)
    {
    case _HEAPEMPTY:
        printf("OK - empty heap\n");
        break;
    case _HEAPEND:
        printf("OK - end of heap\n");
        break;
    case _HEAPBADPTR:
        printf("ERROR - bad pointer to heap\n");
        break;
    case _HEAPBADBEGIN:
        printf("ERROR - bad start of heap\n");
        break;
    case _HEAPBADNODE:
        printf("ERROR - bad node in heap\n");
        break;
    }
    */
    return s;
}
#if 0
#include <psapi.h>
#pragma comment(lib,"Kernel32.lib")
#pragma comment(lib,"Psapi.lib")
static _qword CTOOGetPagefileUsage()
{
    PROCESS_MEMORY_COUNTERS x;
    memset(&x, 0, sizeof(x));
    x.cb = sizeof(x);
    GetProcessMemoryInfo(GetCurrentProcess(), &x, sizeof(x));
    return x.PagefileUsage;
}
#endif
#endif
#endif

#ifdef _WIN32
typedef BOOL (WINAPI * My_t_GetProcessMemoryInfo)(
    HANDLE Process,
    PPROCESS_MEMORY_COUNTERS ppsmemCounters,
    DWORD cb
);
#endif

_qword MaaGetMemoryUsage(_qword* pPeak) noexcept
{
    _qword x = -1;
    if  (pPeak)
    {
        *pPeak = -1;
    }
#ifdef _WIN32
    PROCESS_MEMORY_COUNTERS pmc;
    memset(&pmc, 0, sizeof(pmc));
    pmc.cb = sizeof(pmc);
    static CMaaDllProc dp1, dp2;
    My_t_GetProcessMemoryInfo My_GetProcessMemoryInfo = (My_t_GetProcessMemoryInfo)dp1.Get(L"Kernel32.dll", "GetProcessMemoryInfo");
    My_GetProcessMemoryInfo = My_GetProcessMemoryInfo ? My_GetProcessMemoryInfo : (My_t_GetProcessMemoryInfo)dp2.Get(L"Psapi.dll", "GetProcessMemoryInfo");
    if  (My_GetProcessMemoryInfo)
    {
        My_GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc));
        x = pmc.WorkingSetSize;
        if  (pPeak)
        {
            *pPeak = pmc.PeakWorkingSetSize;
        }
    }
#endif
#ifdef __linux__
    FILE* file = fopen("/proc/self/status", "r");
    if  (file)
    {
        char line[132];
        line[128] = 0;
        int f = 0;
        while(fgets(line, 128, file) && f != 3)
        {
            if  (CMaaIsMemEq(line, "VmSize:", 7))
            {
                // "VmSize:\t   19896 kB"
                f |= 1;
                char* p = line + 7;
                while(*p != 0 && (*p < '0' || *p > '9')) p++;
                int pos = 0;
                if  (mysscanf64(p, &x, -1, &pos, " \t"))
                {
                    p += pos;
                    while(*p == ' ' || *p == '\t') p++;
                    if  (CMaaIsMemEq(p, "kB", 2))
                    {
                        x *= 1024;
                    }
                }
            }
            else if (CMaaIsMemEq(line, "VmPeak:", 7))
            {
                // "VmPeak:\t   19896 kB"
                f |= 2;
                if  (pPeak)
                {
                    char* p = line + 7;
                    while(*p != 0 && (*p < '0' || *p > '9')) p++;
                    int pos = 0;
                    if  (mysscanf64(p, pPeak, -1, &pos, " \t"))
                    {
                        p += pos;
                        while(*p == ' ' || *p == '\t') p++;
                        if  (CMaaIsMemEq(p, "kB", 2))
                        {
                            (*pPeak) *= 1024;
                        }
                    }
                }
            }
        }
        fclose(file);
    }
#endif
#if 0 // MAC
#include<mach/mach.h>

    struct task_basic_info t_info;
    mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT;

    if  (task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count) == KERN_SUCCESS)
    {
        // resident size is in t_info.resident_size;
        // virtual size is in t_info.virtual_size;
        x = t_info.virtual_size;
    }
#endif
    return x;
}


_qword CTOOGetMemoryAllocated()
{
    //return MaaGetMemoryUsage();
#ifdef __unix__
    return CTOOGetHeapAllocated();
    //return CTOOGetMaxRss();
#else
    return 0;
    //return CTOOGetHeapAllocated(); // slow, is not thread safe

    //return CTOOGetPagefileUsage();
#endif
}
//------------------------------------------------------------------------------
